package com.lipaluma.parser;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lipaluma.context.Context;
import com.lipaluma.context.mapping.impl.ColumnMappingManager;
import com.lipaluma.exceptions.CsvParserException;
import com.lipaluma.field.mapper.FieldMapper;
import com.lipaluma.field.mapper.MarshalFieldMapper;
import com.lipaluma.io.SourceWriter;
import com.lipaluma.parser.marshalling.ObjectMarshalled;
import com.lipaluma.util.ReflectionUtils;
import com.lipaluma.xml.mapping.XmlFieldMapping;

public abstract class AbstractMarshallerDataParser<TARGET> implements DataParser<TARGET> {
	private static Logger LOG = LoggerFactory.getLogger(AbstractMarshallerDataParser.class);

	protected Class<TARGET> targetClass;
	protected List<FieldMapper> fieldMappers;
	protected Context context;
	
	protected AbstractMarshallerDataParser() {}
	protected AbstractMarshallerDataParser(Builder<? extends DataParser<TARGET>, TARGET> builder) {
		this.targetClass = builder.targetClass;
		this.context = builder.context;
		this.fieldMappers = new ArrayList<FieldMapper>(builder.mappers);
	}
	
	protected void marshal(TARGET object, SourceWriter writer) {
		ObjectMarshalled objectMarshalled = marshal(object);
		writer.write(objectMarshalled);
	}	
	
	/**
	 * Process an Object and returns a corresponding Row.
	 * 
	 * @param the object
	 * @return the corresponding row
	 */
	protected ObjectMarshalled marshal(Object object) {
		ObjectMarshalled row = new ObjectMarshalled();
		for (FieldMapper fieldMapper : fieldMappers) {
			if(!MarshalFieldMapper.class.isAssignableFrom(fieldMapper.getClass()))
				throw new CsvParserException(fieldMapper.getDescription()+" : the column must be a OneToManyColumnSourceWriter to parse an object to a row");
			if(object != null) {
				Object value = extractValueFromObject(object, fieldMapper.getField());
				injectValueToRow(value, fieldMapper, row);
			}
		}
		return row;
	}
	
	protected Object extractValueFromObject(Object object, Field field) {
		ReflectionUtils.makeAccessible(field);
		return ReflectionUtils.getField(field, object);
	}

	protected void injectValueToRow(Object value, FieldMapper fieldMapper, ObjectMarshalled row) {
		((MarshalFieldMapper)fieldMapper).marshalValue(value, row);
	}
	
	protected FieldMapper createFieldMapper(Context context, Field field, XmlFieldMapping mapping) {
		return context.getMappingManagerByXml().createFieldMapper(field, mapping);
	}

	public static abstract class Builder<PARSER extends DataParser<TARGET>, TARGET> {
		protected Class<TARGET> targetClass;
		protected List<FieldMapper> mappers = new ArrayList<FieldMapper>();
		protected Context context = new Context(new ColumnMappingManager());
		
		protected Builder(Class<TARGET> targetClass, Context context) {
			this.targetClass = targetClass;
			this.context = context;
		}

		public Builder<PARSER, TARGET> withContext(Context context) {
			this.context = context;
			return this;
		}
		public Builder<PARSER, TARGET> withFieldMappers(List<FieldMapper> mappers) {
			this.mappers.addAll(mappers);
			return this;
		}
		
		public abstract PARSER build();
	}
}
